﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: SqlQueryCommandBuilder.cs
//
//  Description: Compiles SqlQuery instances to storage queries.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Text;
using Chili.Opf3.Query;

namespace Chili.Opf3.Storages
{
	/// <summary>
	/// Compiles a <see cref="Opf3.Query.SqlQuery">SqlQuery</see> to a storage dependent query.
	/// </summary>
	/// <remarks>
	/// Compiles a <see cref="Opf3.Query.SqlQuery">SqlQuery</see> to a storage dependent
	/// <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>. This class is used
	/// internally by the <see cref="Opf3.Query.SqlQuery">SqlQuery's</see> 
	/// <see cref="Opf3.Query.SqlQuery.ToCompiledQuery">ToCompiledQuery</see> method.
	/// </remarks>
	public sealed class SqlQueryCommandBuilder : IStorageCommandBuilder
	{
		#region IStorageCommandBuilder Members

		/// <summary>
		/// Converts the given query to a storage dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>. 
		/// This method should only be called by a class that implements the <see cref="IQuery">IQuery</see> 
		/// interface.
		/// </summary>
		/// <param name="query"><see cref="Opf3.Query.IQuery">Query</see> object
		/// that is converted to a storage dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>.
		/// </param>
		/// <param name="storage">The storage that the query is compiled for.</param>
		/// <returns>A storage dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>.</returns>
		public CompiledQuery ToCompiledQuery(IStorage storage, IQuery query)
		{
			if (storage == null)
				throw new ArgumentNullException("storage");
			if (query == null)
				throw new ArgumentNullException("query");
			SqlQuery sq = query as SqlQuery;
			if (sq == null)
				throw new NotSupportedException("Only SqlQuery supported by this class.");
			SqlStorageBase ssb = storage as SqlStorageBase;
			if (ssb == null)
				throw new NotSupportedException("Only classes that inherit from SqlStorageBase are supported by this class.");

			// Create a command object.
			IDbCommand command = ssb.CreateCommand();
			if (command == null)
				throw new NullReferenceException("Storage returned null for command.");

			// Set the command type.
			command.CommandType = sq.CommandType;

			// Enter only if parameter are present.
			if (sq.Parameters != null)
			{
				StringBuilder c = new StringBuilder(sq.Command);

				// Check for all {x} in the text.
				Regex r = new Regex("{.+?}", RegexOptions.IgnoreCase | RegexOptions.Singleline);
				MatchCollection matches = r.Matches(sq.Command);

				// Loop over the matches.
				for (int i = matches.Count - 1; i >= 0; i--)
				{
					Match match = matches[i];

					// Check if the found tag is contains a number.
					Type type = null;
					int result = 0;
					if (!ParseMatch(match.Value, out result, out type))
						continue;

					// Check if the number represents an index in the list of parameter.
					if (result >= sq.Parameters.Count || result < 0)
						throw new IndexOutOfRangeException("The specified parameter index could not be found. Make sure the index starts with 0 and is within valid ranges.");

					// get the parameter and extract the value and name.
					object paramValue = sq.Parameters[result];
					string paramName = "p" + i;

					// Create a storage dependent parameter.
					IDataParameter dataParameter = ssb.CreateParameter(i, ref paramName, paramValue, 
						(paramValue != null ? paramValue.GetType() : type));

					// Do some string manipulation to add the paramenter name.
					c = c.Replace(match.Value, paramName, match.Index, match.Length);

					command.Parameters.Insert(0, dataParameter);
				}

				// Set the text of the command.
				command.CommandText = c.ToString();
			}
			else
			{
				// Set the current command as command text.
				command.CommandText = sq.Command;
			}

			CompiledQuery cq = new CompiledQuery(command, query.QueryType);
			cq.Timeout = sq.Timeout;
			return cq;
		}

		/// <summary>
		/// Parses the match.
		/// </summary>
		/// <param name="match">The match that is parsed.</param>
		/// <param name="index">The index of the current match.</param>
		/// <param name="type">The type of the current match.</param>
		/// <returns>True if the parse process was successful.</returns>
		private bool ParseMatch(string match, out int index, out Type type)
		{
			type = typeof(object);
			// Remove the { } brackets.
			match = match.Substring(1, match.Length - 2);

			// Parse the type.
			string[] items = match.Split(':');
			if (items.Length > 1)
			{
				type = Type.GetType("System." + items[1], true);
			}

			// Parse the index.
			return int.TryParse(items[0], out index);
		}

		/// <summary>
		/// Returns whether the <see cref="IStorageCommandBuilder">IStorageCommandBuilder</see> supports 
		/// the query type on the given storage.
		/// </summary>
		/// <param name="storage">The storage for which the query should be compiled.</param>
		/// <param name="query">The query that should be compiled to a storage dependent command.</param>
		/// <returns>True if the query can be parsed by the <see cref="IStorageCommandBuilder">IStorageCommandBuilder</see>.</returns>
		public bool IsSupported(IStorage storage, IQuery query)
		{
			return (storage is SqlStorageBase && query is SqlQuery);
		}

		#endregion
	}
}
